﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using TestcraftLibrary.Common;
using Testcraft.Scene;

namespace Testcraft
{
    class Chunk : IDisposable
    {
        public enum CompressionStatus
        {
            Compressed,
            Compressing,
            Decompressed,
            Decompressing,
        }
        public enum ChunkStatus
        {
            Initializing,
            Initialized,
            Ready
        }

        public CompressionStatus compressionStatus = CompressionStatus.Decompressed;
        public ChunkStatus status;
        public ChunkEngine engine;
        private GraphicsDevice device;
        public const int ChunkHeight = 256;
        public const int ChunkWidth = 16;
        private const int ChunkPartCount = 16;
        public bool IsCompressed { get; private set; }
        public System.IO.MemoryStream compressionStream;

        public Chunk LeftNeighbor = null;
        public Chunk RightNeighbor = null;
        public Chunk FrontNeighbor = null;
        public Chunk BackNeighbor = null;

        public BoundingBox Bounds { get; private set; }

        public ChunkPartEx2[] partsEx2;

        public bool Initialized { get; set; }


        public byte ActiveChunkParts { get; set; }

        public int X { get; private set; }
        public int Y { get; private set; }

        public Chunk(ChunkEngine parent, GraphicsDevice device, int x, int y)
        {
            
            this.compressionStream = new System.IO.MemoryStream();
            this.engine = parent;
            this.X = x;
            this.Y = y;

            this.device = device;
            this.partsEx2 = new ChunkPartEx2[ChunkPartCount];

            this.ActiveChunkParts = 0;

            //this.octree = new BlockOctree(this);

            Vector3 min = new Vector3(this.X * Chunk.ChunkWidth, 0, this.Y * Chunk.ChunkWidth);
            Vector3 max = min + new Vector3(Chunk.ChunkWidth, Chunk.ChunkHeight, Chunk.ChunkWidth);

            this.Bounds = new BoundingBox(min, max);
        }

        public void InitializeChunkPart(int index)
        {
            int partHeight = 16;
            Vector3 minBounds = new Vector3(this.X * 16, index * partHeight, this.Y * 16);
            Vector3 maxBounds = minBounds + new Vector3(16, partHeight, 16);

            this.partsEx2[index] = new ChunkPartEx2(device, this, index, new BoundingBox(minBounds, maxBounds), 16, (short)partHeight);
            this.ActiveChunkParts++;
        }

        public void AddBlocks(BlockDescriptorAdd[] blocks)
        {
            int partHeight = 16;
            for (int i = 0; i < 16; i++)
            {
                IEnumerable<BlockDescriptorAdd> chunkPartBlocks = blocks.Where(block => block.Location.Y >= (i * partHeight) && block.Location.Y < ((i + 1) * partHeight));

                if (chunkPartBlocks.Any())
                {
                    if(partsEx2[i] == null)
                        this.InitializeChunkPart(i);

                    foreach (BlockDescriptorAdd descriptor in chunkPartBlocks)
                        this.partsEx2[i].BlocksPendingAdd.Enqueue(descriptor);

                    this.partsEx2[i].Initialize();
                }
            }

            //this.octree.blocks.AddRange(blocks);
            //this.octree.Build();
        }

        public unsafe void Compress()
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            const int blockMatrixByteSize = Chunk.ChunkWidth * Chunk.ChunkWidth * Chunk.ChunkWidth * sizeof(ushort);
            const int faceMatrixByteSize = Chunk.ChunkWidth * Chunk.ChunkWidth * Chunk.ChunkWidth * sizeof(byte);
            byte[] blockMatrix = new byte[blockMatrixByteSize + 1];
            byte[] faceMatrix = new byte[faceMatrixByteSize];

            using (System.IO.Compression.GZipStream gzipStream = new System.IO.Compression.GZipStream(this.compressionStream, System.IO.Compression.CompressionMode.Compress, true))
            {
                for (int i = 0; i < this.partsEx2.Length; i++)
                {
                    //this.compressionStream.WriteByte((byte)i);
                    if (this.partsEx2[i] != null)
                    {
                        Buffer.BlockCopy(this.partsEx2[i].blocks, 0, blockMatrix, 1, blockMatrixByteSize);
                        blockMatrix[0] = (byte)i;
                        gzipStream.Write(blockMatrix, 0, blockMatrixByteSize + 1);

                        Buffer.BlockCopy(this.partsEx2[i].blockFaces, 0, faceMatrix, 0, faceMatrixByteSize);
                        gzipStream.Write(faceMatrix, 0, faceMatrixByteSize);

                        
                        this.partsEx2[i].Dispose();
                        
                        this.partsEx2[i] = null;
                    }
                }
            }


            this.IsCompressed = true;
            this.compressionStatus = CompressionStatus.Compressed;
            watch.Stop();
            //Console.WriteLine("Compression: {0}MS", watch.ElapsedMilliseconds);
        }

        public void Decompress()
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            const int blockMatrixByteSize = Chunk.ChunkWidth * Chunk.ChunkWidth * Chunk.ChunkWidth * sizeof(ushort);
            const int faceMatrixByteSize = Chunk.ChunkWidth * Chunk.ChunkWidth * Chunk.ChunkWidth * sizeof(byte);

            int decompressedSize = (blockMatrixByteSize + 1 + faceMatrixByteSize) * this.ActiveChunkParts;
            byte[] decompressedData = new byte[decompressedSize];



            this.compressionStream.Position = 0;
            while (this.compressionStream.Position < this.compressionStream.Length)
            {
                using (System.IO.Compression.GZipStream gzipStream = new System.IO.Compression.GZipStream(this.compressionStream, System.IO.Compression.CompressionMode.Decompress, true))
                {
                    gzipStream.Read(decompressedData, 0, decompressedSize);
                }

                int stride = blockMatrixByteSize + 1 + faceMatrixByteSize;
                for (int i = 0; i < this.ActiveChunkParts; i++)
                {
                    int index = decompressedData[i * stride];
                    Vector3 minBounds = new Vector3(this.X * 16, index * 16, this.Y * 16);
                    Vector3 maxBounds = minBounds + new Vector3(16, 16, 16);
                    this.partsEx2[index] = new ChunkPartEx2(this.device, this, index, new BoundingBox(minBounds, maxBounds), 16, 16);
                    Buffer.BlockCopy(decompressedData, (i * stride) + 1, this.partsEx2[index].blocks, 0, blockMatrixByteSize);
                    Buffer.BlockCopy(decompressedData, (i * stride) + 1 + blockMatrixByteSize, this.partsEx2[index].blockFaces, 0, faceMatrixByteSize);

                    this.partsEx2[index].UpdateBuffersUsingCompositeFaces();
                }
            }

            this.compressionStream.SetLength(0);

            this.IsCompressed = false;
            this.compressionStatus = CompressionStatus.Decompressed;
            watch.Stop();
            //Console.WriteLine("Decompression: {0}MS", watch.ElapsedMilliseconds);
        }

        public void Initialize()
        {
            for (int i = 0; i < this.partsEx2.Length; i++)
            {
                if (this.partsEx2[i] != null)
                {
                    this.partsEx2[i].Initialize();
                }
            }

            this.Initialized = true;
            this.status = Chunk.ChunkStatus.Initialized;
        }

        public void Dispose()
        {
            for (int i = 0; i < this.partsEx2.Length; i++)
            {
                if (this.partsEx2[i] != null)
                {
                    this.partsEx2[i].Dispose();
                    this.partsEx2[i] = null;
                }
            }

            this.compressionStream.Dispose();
            this.engine = null;
        }
    }
}
